import logging, sys, subprocess, os, shutil, formencode, tools

from formencode import validators
from pylons import request, response, session, config, tmpl_context as c
from pylons.controllers.util import abort, redirect_to, Response, Request
from pylons.decorators import validate

from webca.lib.base import BaseController, render

import webca.lib.helpers as h

log = logging.getLogger(__name__)

class CaController(BaseController):
    # todo password protect access to this function
    def sign_req(self):
        c.title = 'CSR Management'
        c.page_name = 'CSR Management'
        c.footer = h.link_to('Boot Strap CA', 'boot_strap') + ' | ' + h.link_to('Request Management', 'make_req') + ' | ' + h.link_to('CSR Management', 'sign_req') + ' | ' + h.link_to('Certificate Management', 'certs')
        
        csr_list = []
        files = []
        
        if os.path.exists(config['pylons.app_globals'].csr_dir):
            files = os.listdir(config['pylons.app_globals'].csr_dir)
        
        for file in files:
            csr_list.append(file.__str__())
        
        c.csr_list = csr_list
                       
        if request.params.get('submit') == 'print_req':
            temp = ''
                
            for i, csr in enumerate(csr_list):
                if request.params.get(repr(i)) is not None:
                    cmd = [config['pylons.app_globals'].openssl_bin, 'req', '-text', '-noout', '-in', config['pylons.app_globals'].csr_dir + '/' + csr]
                        
                    if config['ca_debug']: print cmd
                                                                      
                    try: 
                        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                        temp += proc.communicate()[0]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
                    
            c.stdout_value = temp
        # is the request a sign request             
        elif request.params.get('submit') == 'sign_req':
            for i, csr in enumerate(csr_list):
                if request.params.get(repr(i)) is not None:
                    cmd = [config['pylons.app_globals'].openssl_bin, 'ca', '-config', config['openssl_config'], '-out', config['pylons.app_globals'].certs + '/' + csr.rstrip('.csr') + '.crt', '-in', config['pylons.app_globals'].csr_dir + '/' + csr, '-batch', '-passin', 'pass:' + config['ca_pass']]
                    print cmd
                    
                    try:
                        proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                        c.stdout_value = proc.communicate()[1]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print 'removing', config['pylons.app_globals'].csr_dir + '/' + csr 
                            os.remove(config['pylons.app_globals'].csr_dir + '/' + csr)
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
        # is the request a delete request?
        elif request.params.get('submit') == 'del_req':
            temp = ''

            for i, csr in enumerate(csr_list):
                if request.params.get(repr(i)) is not None:
                    # delete csr 
                    os.remove(config['pylons.app_globals'].csr_dir + '/' + csr)
                    temp += 'deleted:' + repr(csr) + '\n'
                    # delete keys
                    os.remove(config['pylons.app_globals'].key_dir + '/' + csr.rstrip('.csr') + '.key')
                    temp += 'deleted:' + repr(csr.rstrip('.csr') + '.key') + '\n'

            c.stdout_value = temp

            if config['ca_debug']: print temp
                                                        
        return render('sign_req.mako')

    def certs(self):
        c.title = 'Certificate Management'
        c.page_name = 'Certificate Management'
        c.footer = h.link_to('Boot Strap CA', 'boot_strap') + ' | ' + h.link_to('Request Management', 'make_req') + ' | ' + h.link_to('CSR Management', 'sign_req') + ' | ' + h.link_to('Certificate Management', 'certs')
        cert_list = []
        files = []
        
        if os.path.exists(config['pylons.app_globals'].certs):
            files = os.listdir(config['pylons.app_globals'].certs)
        
        for file in files:
            cert_list.append(file.__str__())
        
        c.cert_list = cert_list
                       
        if request.params.get('submit') == 'print_cert':
            temp = ''
                
            for i, cert in enumerate(cert_list):
                if request.params.get(repr(i)) is not None:
                    cmd = [
                           config['pylons.app_globals'].openssl_bin, 
                           'x509', 
                           '-text', 
                           '-noout', 
                           '-in', config['pylons.app_globals'].certs + '/' + cert
                           ]
                        
                    if config['ca_debug']: print cmd
                                                                      
                    try: 
                        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                        temp += proc.communicate()[0]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
                    
            c.stdout_value = temp

        elif request.params.get('submit') == 'revoke_cert':
            temp = ''
                
            for i, cert in enumerate(cert_list):
                if request.params.get(repr(i)) is not None:
                    cmd = [
                           config['pylons.app_globals'].openssl_bin, 
                           'ca',
                           '-revoke', config['pylons.app_globals'].certs + '/' + cert, 
                           '-config', config['openssl_config'],  
                           '-batch', 
                           '-key', config['ca_pass']
                           ]
                        
                    if config['ca_debug']: print cmd
                                                                      
                    try: 
                        proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                        temp += proc.communicate()[1]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
                    
            c.stdout_value = temp
        
        elif request.params.get('submit') == 'print_cert_db':
            c.stdout_value = 'not yet implemented'    
    
    
        return render('certs.mako')

    # todo: password protect access to this function
    def boot_strap(self):
        c.title = 'Boot Strap CA'
        c.page_name = 'Boot Strap CA'
        c.footer = h.link_to('Boot Strap CA', 'boot_strap') + ' | ' + h.link_to('Request Management', 'make_req') + ' | ' + h.link_to('CSR Management', 'sign_req') + ' | ' + h.link_to('Certificate Management', 'certs')

        c.ca_base_dir = config['pylons.app_globals'].dir
        c.ca_cert_dir = config['pylons.app_globals'].certs
        c.ca_crl_dir = config['pylons.app_globals'].crl_dir
        c.ca_database = config['pylons.app_globals'].database
        c.ca_new_certs_dir = config['pylons.app_globals'].new_certs_dir
        c.ca_key_dir = config['pylons.app_globals'].key_dir
        c.ca_csr_dir = config['pylons.app_globals'].csr_dir
        c.ca_serial = config['pylons.app_globals'].serial 
        c.ca_crlnumber = config['pylons.app_globals'].crlnumber 
        
        if request.params.get('submit') == 'boot_strap':
            if os.path.exists(config['pylons.app_globals'].key_dir + '/rootCA.key') and os.path.exists(config['pylons.app_globals'].certs + '/rootCA.crt'):
                c.stdout_value = 'CA has already been boot straped, at least there is a root cert and a root key'
                print 'CA has already been boot straped, at least there is a root cert and a root key'
            else:
                print 'boot straping CA...'
                
                tools.make_folder(config['pylons.app_globals'].dir)
                tools.make_folder(config['pylons.app_globals'].certs)
                tools.make_folder(config['pylons.app_globals'].crl_dir)
                tools.make_file(config['pylons.app_globals'].database)
                tools.make_folder(config['pylons.app_globals'].new_certs_dir)
                tools.make_folder(config['pylons.app_globals'].key_dir)
                tools.make_folder(config['pylons.app_globals'].csr_dir)
                tools.make_file(config['pylons.app_globals'].serial)
                tools.make_file(config['pylons.app_globals'].crlnumber)
                
                f = open(config['pylons.app_globals'].crlnumber, 'w')
                f.write('01\n')
                f.close()    
                
                f = open(config['pylons.app_globals'].serial, 'w')
                f.write('01\n')
                f.close()  
                
                subject = tools.build_subject(request.params.get('ca_country'), request.params.get('ca_state'), request.params.get('ca_location'), request.params.get('ca_organisation'), request.params.get('ca_common_name'))
                                               
                cmd = [ 
                        config['pylons.app_globals'].openssl_bin, 
                        'req', 
                        '-config', config['openssl_config'], 
                        '-new', 
                        '-newkey', 'rsa:2048', 
                        '-x509', 
                        '-extensions', request.params.get('ca_ext_section'), 
                        '-keyout', config['pylons.app_globals'].key_dir + '/rootCA.key', 
                        '-out', config['pylons.app_globals'].certs + '/rootCA.crt', 
                        '-batch', 
                        '-passout', 
                        'pass:' + config['ca_pass'], 
                        '-subj', subject, 
                        '-days', '365'
                    ]
                
                try:
                    proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                    c.stdout_value = proc.communicate()[1]
                                                                       
                    if proc.returncode < 0:
                        message = 'Child was terminated b signal', proc.returncode
                        print >> sys.stderr, message 
                    else:
                        message = 'Child returned', proc.returncode
                        print >> sys.stderr, message
                except OSError, e:
                        message = 'Execution failed:', e
                        print >> sys.stderr, message
        
        elif request.params.get('submit') == 'print_ca':
            print 'requesting print_ca'
            root_cert = config['pylons.app_globals'].certs + '/rootCA.crt'

            if os.path.exists(root_cert):
                 cmd = [
                        config['pylons.app_globals'].openssl_bin, 
                        'x509', 
                        '-noout', 
                        '-text', 
                        '-in', root_cert
                        ]
                 
                 if config['ca_debug']: print repr(cmd)
            
                 try:
                    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                    c.stdout_value = proc.communicate()[0]
                                                                                          
                    if proc.returncode < 0:
                        message = 'Child was terminated b signal', proc.returncode
                        print >> sys.stderr, message 
                    else:
                        message = 'Child returned', proc.returncode
                        print >> sys.stderr, message
                 except OSError, e:
                     message = 'Execution failed:', e
                     print >> sys.stderr, message
            else:
                c.stdout_value = 'root cert ist not there, please boot strap first'

        elif request.params.get('submit') == 'get_ca_cert':
            print 'requesting download root ca'
            filename = config['pylons.app_globals'].certs + '/rootCA.crt'
        
            if os.path.exists(filename):
                print 'get_ca_cert(): file exists'
                fh = open(filename, 'r')
                data = fh.read()
    
                response.content_type = 'application/octet-stream'
                response.headers['content-length'] = len(data)
                response.headers['content-disposition'] = 'attachment; filename="rootCA.crt"'
                response.content = data
            else:
                c.stdout_value = 'cert does not yet exists, please boot strap first'
        
        elif request.params.get('submit') == 'print_ca_config':
            fh = open(config['openssl_config'], 'r')
            c.stdout_value = fh.read()
                    
        return render('boot_strap.mako')
    
    def make_req(self):
        c.title = 'Client Request Management'
        c.page_name = 'Client Request Management'
        c.footer = h.link_to('Boot Strap CA', 'boot_strap') + ' | ' + h.link_to('Request Management', 'make_req') + ' | ' + h.link_to('CSR Management', 'sign_req') + ' | ' + h.link_to('Certificate Management', 'certs')
        
        c.stderr_value = ''

        # todo: validate user input using formencode schema
        if request.params.get('submit') == 'make_req':
            if config['ca_debug']: print 'make_req() -> make_req'
            
            country_validator       = validators.String(max=2)
            state_validator         = validators.NotEmpty()
            location_validator      = validators.NotEmpty()
            organisation_validator  = validators.NotEmpty()
            common_name_validator   = validators.NotEmpty()
           
            try:
                c.country       = request.params.get('country')
                c.state         = request.params.get('state')
                c.location      = request.params.get('location')
                c.organisation  = request.params.get('organisation')
                c.common_name   = request.params.get('common_name')
                
                country_validator.to_python(c.country) 
                state_validator.to_python(c.state)
                location_validator.to_python(c.location)
                organisation_validator.to_python(c.organisation)
                common_name_validator.to_python(c.common_name)
            
                subject = tools.build_subject(str(c.country), str(c.state), str(c.location), str(c.organisation), str(c.common_name))
                    
                cmd = [
                       config['pylons.app_globals'].openssl_bin, 
                       'req', 
                       '-new', 
                       '-nodes', 
                       '-keyout', config['pylons.app_globals'].key_dir + '/' + str(c.common_name) + '.key', 
                       '-out', config['pylons.app_globals'].csr_dir + '/' + str(c.common_name) + '.csr', 
                       '-subj', subject
                      ]
            
                if config['ca_debug']: print cmd
            
                try:
                    proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                    c.stderr_value = proc.communicate()[1]
                               
                    if proc.returncode < 0:
                        message = 'Child was terminated b signal', proc.returncode
                        print >> sys.stderr, message 
                    else:
                        message = 'Child returned', proc.returncode
                        print >> sys.stderr, message
                except OSError, e:
                    message = 'Execution failed:', e
                    print >> sys.stderr, message
             
            except formencode.Invalid, e:
                if config['ca_debug']: print e
                c.stderr_value = e 
        
        # todo: validate if uploaded data is an CSR in PEM format  
        # todo: disable upload button if no file has been choosen     
        elif request.params.get('submit') == 'upload':
            if config['ca_debug']: 
                print 'make_req() -> upload'
                print 'POST:', request.POST['filename']
            
            if 'filename' in request.POST:
                if config['ca_debug']: print 'filename not in request.POST'

                file_object = request.POST['filename']
                print os.path.join(config['pylons.app_globals'].csr_dir, file_object.filename.lstrip(os.sep))
                                   
                if not os.path.exists(os.path.join(config['pylons.app_globals'].csr_dir, file_object.filename.lstrip(os.sep))):
                    permanent_file = open(os.path.join(config['pylons.app_globals'].csr_dir, file_object.filename.lstrip(os.sep)), 'w')
                    shutil.copyfileobj(file_object.file, permanent_file)
                
                    permanent_file.close()
                    
                    print 'Successfully uploaded: %s, size: %i' % (file_object.filename, len(file_object.value))
                    c.stderr_value = 'Successfully uploaded: %s, size: %i bytes' % (file_object.filename, len(file_object.value))
                else:
                    print 'File does already exist, cannot upload'
                    c.stderr_value =  'File does already exist, cannot upload'
            else:
                if config['ca_debug']: print 'in else...'            
    
                c.stderr_value = 'choose file first...'
                
        return render('make_req.mako')
